<?php
/*****************************************************************************
Copyright © 2008 The Regents of the University of Nevada
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

class AdminController extends Controller {

	protected $validation_rules = array(
		'debug_mode' => array(
			'name' => 'Debug mode',
			'required' => false,
			'maxlength' => null,
			'min' => null,
			'max' => null,
			'allowed_values' => array('true', null)
		),
		'storage_engine' => array(
			'name' => 'Storage engine',
			'phptype' => 'string',
			'required' => true,
			'maxlength' => null,
			'min' => null,
			'max' => null,
			'allowed_values' => array('xmlfile', 'rdbms')
		),
		'dbengine' => array(
			'name' => 'DBMS',
			'phptype' => 'string',
			'required' => array('storage_engine' => 'rdbms'),
			'maxlength' => null,
			'min' => null,
			'max' => null,
			'allowed_values' => array('mysql', 'pgsql')
		),
		'dbhost' => array(
			'name' => 'Database host',
			'required' => array('storage_engine' => 'rdbms'),
			'maxlength' => 200,
			'min' => null,
			'max' => null,
			'allowed_values' => null
		),
		'dbport' => array(
			'name' => 'Database port',
			'required' => false,
			'maxlength' => 5,
			'min' => 2,
			'max' => 65536,
			'allowed_values' => null
		),
		'dbname' => array(
			'name' => 'Database name',
			'required' => array('storage_engine' => 'rdbms'),
			'maxlength' => 50,
			'min' => null,
			'max' => null,
			'allowed_values' => null
		),
		'dbuser' => array(
			'name' => 'Database user',
			'required' => array('storage_engine' => 'rdbms'),
			'maxlength' => 50,
			'min' => null,
			'max' => null,
			'allowed_values' => null
		),
		'dbpass' => array(
			'name' => 'Database password',
			'required' => array('storage_engine' => 'rdbms'),
			'maxlength' => 50,
			'min' => null,
			'max' => null,
			'allowed_values' => null
		),
		'num_coordinate_digits' => array(
			'name' => 'Number of coordinate digits to display',
			'required' => true,
			'maxlength' => 1,
			'min' => 0,
			'max' => 9,
			'allowed_values' => null
		),
		'enforce_bounds' => array(
			'name' => 'Enforce bounds',
			'required' => false,
			'maxlength' => null,
			'min' => null,
			'max' => null,
			'allowed_values' => array('true', null)
		),
		'pan_factor' => array(
			'name' => 'Pan factor',
			'required' => true,
			'maxlength' => null,
			'min' => 0.00001,
			'max' => 10,
			'allowed_values' => null
		),
		'scale_factor' => array(
			'name' => 'Scale factor',
			'required' => true,
			'maxlength' => null,
			'min' => 0.00001,
			'max' => 10,
			'allowed_values' => null
		),
		'canvas_n' => array(
			'name' => 'Canvas north bound',
			'required' => true,
			'maxlength' => null,
			'min' => -90,
			'max' => 90,
			'allowed_values' => null
		),
		'canvas_s' => array(
			'name' => 'Canvas south bound',
			'required' => true,
			'maxlength' => null,
			'min' => -90,
			'max' => 90,
			'allowed_values' => null
		),
		'canvas_e' => array(
			'name' => 'Canvas east bound',
			'required' => true,
			'maxlength' => null,
			'min' => -180,
			'max' => 180,
			'allowed_values' => null
		),
		'canvas_w' => array(
			'name' => 'Canvas west bound',
			'required' => true,
			'maxlength' => null,
			'min' => -180,
			'max' => 180,
			'allowed_values' => null
		),
		'iv_n' => array(
			'name' => 'Initial view north bound',
			'required' => true,
			'maxlength' => null,
			'min' => -90,
			'max' => 90,
			'allowed_values' => null
		),
		'iv_s' => array(
			'name' => 'Initial view south bound',
			'required' => true,
			'maxlength' => null,
			'min' => -90,
			'max' => 90,
			'allowed_values' => null
		),
		'iv_e' => array(
			'name' => 'Initial view east bound',
			'required' => true,
			'maxlength' => null,
			'min' => -180,
			'max' => 180,
			'allowed_values' => null
		),
		'iv_w' => array(
			'name' => 'Initial view west bound',
			'required' => true,
			'maxlength' => null,
			'min' => -180,
			'max' => 180,
			'allowed_values' => null
		),
		'min_coverage_ns' => array(
			'name' => 'Minimum north-south coverage',
			'required' => true,
			'maxlength' => null,
			'min' => 0,
			'max' => 180,
			'allowed_values' => null
		),
		'min_coverage_ew' => array(
			'name' => 'Minimum east-west coverage',
			'required' => true,
			'maxlength' => null,
			'min' => 0,
			'max' => 360,
			'allowed_values' => null
		),
		'frame_n' => array(
			'name' => 'Initial selection frame north bound',
			'required' => true,
			'maxlength' => null,
			'min' => -90,
			'max' => 90,
			'allowed_values' => null
		),
		'frame_s' => array(
			'name' => 'Initial selection frame south bound',
			'required' => true,
			'maxlength' => null,
			'min' => -90,
			'max' => 90,
			'allowed_values' => null
		),
		'frame_e' => array(
			'name' => 'Initial selection frame east bound',
			'required' => true,
			'maxlength' => null,
			'min' => -180,
			'max' => 180,
			'allowed_values' => null
		),
		'frame_w' => array(
			'name' => 'Initial selection frame west bound',
			'required' => true,
			'maxlength' => null,
			'min' => -180,
			'max' => 180,
			'allowed_values' => null
		),
		'resizer_thickness' => array(
			'name' => 'Resizer thickness',
			'required' => true,
			'maxlength' => null,
			'min' => 0,
			'max' => 90,
			'allowed_values' => null
		),
		'grid_maxwords' => array(
			'name' => 'Max # of words to display in metadata (grid view)',
			'required' => true,
			'maxlength' => null,
			'min' => 4,
			'max' => 1000,
			'allowed_values' => null
		),
		'tile_maxwords' => array(
			'name' => 'Max # of words to display in metadata (tile view)',
			'required' => true,
			'maxlength' => null,
			'min' => 4,
			'max' => 1000,
			'allowed_values' => null
		),
		'map_outlines_limit' => array(
			'name' => 'Max # of map outlines to display in results map',
			'required' => true,
			'maxlength' => null,
			'min' => 0,
			'max' => 5000,
			'allowed_values' => null
		),
		'grid_rows' => array(
			'name' => 'Rows in grid view',
			'required' => true,
			'maxlength' => null,
			'min' => 2,
			'max' => 100,
			'allowed_values' => null
		),
		'tile_rows' => array(
			'name' => 'Rows in tile view',
			'required' => true,
			'maxlength' => null,
			'min' => 2,
			'max' => 50,
			'allowed_values' => null
		),
		'grid_cols' => array(
			'name' => 'Columns in tile view',
			'required' => true,
			'maxlength' => null,
			'min' => 2,
			'max' => 8,
			'allowed_values' => null
		),
		'field_name' => array(
			'name' => 'Field name',
			'required' => true,
			'maxlength' => 50,
			'min' => null,
			'max' => null,
			'allowed_values' => null
		),
		'field_display' => array(
			'name' => 'Field display',
			'required' => false,
			'maxlength' => null,
			'min' => null,
			'max' => null,
			'allowed_values' => array('true', null)
		),
	);


	public function __construct() {
		parent::__construct();
		header("Expires: 0");
		header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
		header("cache-control: no-store, no-cache, must-revalidate");
		header("Pragma: no-cache");
	}


	// override
	protected function checkSanity() {
		try {
			// make sure the prefs are available
			$p = Preference::getInstance();
		}
		catch (PreferencesException $e) {
			CustomException::add($e);
			require_once($_SERVER['DOCUMENT_ROOT'] . '/'
				. trim(Config::ISIS_FOLDER_ROOT, '/') . '/admin/error.php');
			die;
		}
		catch (DataStoreException $e) {
			CustomException::add($e);
			require_once($_SERVER['DOCUMENT_ROOT'] . '/'
				. trim(Config::ISIS_FOLDER_ROOT, '/') . '/admin/error.php');
			die;
		}
	}


	public function editSettings() {
		if (!User::isLoggedIn()) {
			self::redirect('../login.php');
		}
		if (!array_key_exists('edit', $_POST)) {
			return false;
		}

		if (!$this->validate($_POST)) return false;

		Preference::getInstance()->setDebugMode(
			($_POST['debug_mode'] == 'true') ? true : false);
		Preference::getInstance()->setStorageEngine($_POST['storage_engine']);
		if (array_key_exists('dbengine', $_POST))
			Preference::getInstance()->setDBEngine($_POST['dbengine']);
		if (array_key_exists('dbhost', $_POST))
			Preference::getInstance()->setDBHost($_POST['dbhost']);
		if (array_key_exists('dbport', $_POST))
			Preference::getInstance()->setDBPort($_POST['dbport']);
		if (array_key_exists('dbname', $_POST))
			Preference::getInstance()->setDBName($_POST['dbname']);
		if (array_key_exists('dbuser', $_POST))
			Preference::getInstance()->setDBUser($_POST['dbuser']);
		if (array_key_exists('dbpass', $_POST))
			Preference::getInstance()->setDBPass($_POST['dbpass']);
		Preference::getInstance()->setNumMapCoordinateDigits(
			(int) $_POST['num_coordinate_digits']);
		Preference::getInstance()->setEnforcePanMapBounds(
			($_POST['enforce_bounds'] == 'true') ? true : false);
		Preference::getInstance()->setPanFactor((float) $_POST['pan_factor']);
		Preference::getInstance()->setScaleFactor((float) $_POST['scale_factor']);
		Preference::getInstance()->setMapLatN((float) $_POST['canvas_n']);
		Preference::getInstance()->setMapLatS((float) $_POST['canvas_s']);
		Preference::getInstance()->setMapLongE((float) $_POST['canvas_e']);
		Preference::getInstance()->setMapLongW((float) $_POST['canvas_w']);
		Preference::getInstance()->setInitialViewLatN((float) $_POST['iv_n']);
		Preference::getInstance()->setInitialViewLatS((float) $_POST['iv_s']);
		Preference::getInstance()->setInitialViewLongE((float) $_POST['iv_e']);
		Preference::getInstance()->setInitialViewLongW((float) $_POST['iv_w']);
		Preference::getInstance()->setMapMinCoverageNS(
			(float) $_POST['min_coverage_ns']);
		Preference::getInstance()->setMapMinCoverageEW(
			(float) $_POST['min_coverage_ew']);
		Preference::getInstance()->setDefaultFrameLatN((float) $_POST['frame_n']);
		Preference::getInstance()->setDefaultFrameLongW(
			(float) $_POST['frame_w']);
		Preference::getInstance()->setDefaultFrameHeight(
			(float) $_POST['frame_n'] - (float) $_POST['frame_s']);
		Preference::getInstance()->setDefaultFrameWidth(
			(float) $_POST['frame_e'] - (float) $_POST['frame_w']);
		Preference::getInstance()->setResizerThickness(
			(float) $_POST['resizer_thickness']);
		Preference::getInstance()->setGridMaxWords((int) $_POST['grid_maxwords']);
		Preference::getInstance()->setTileMaxWords((int) $_POST['tile_maxwords']);
		Preference::getInstance()->setResultsMapOutlinesLimit(
			(int) $_POST['map_outlines_limit']);
		Preference::getInstance()->setGridRows((int) $_POST['grid_rows']);
		Preference::getInstance()->setTileRows((int) $_POST['tile_rows']);
		Preference::getInstance()->setTileColumns((int) $_POST['tile_cols']);
		if (is_array($_POST['field_name'])) {
			Preference::getInstance()->setFieldNames($_POST['field_name']);
		}
		if (is_array($_POST['field_display'])
		&& is_array($_POST['all_field_mappings'])) {
			$display = array();
			foreach ($_POST['all_field_mappings'] as $m) {
				//if (!array_key_exists($m, $_POST['field_display'])) continue;
				$display[$m] = ($_POST['field_display'][$m] == 'true')
					? true : false;
			}
			Preference::getInstance()->setFieldDisplay($display);
		}

		try {
			if (Preference::getInstance()->save()) {
				self::setFlash(String::SETTINGS_SAVED);
			}
		}
		catch (DataStoreException $e) {
			self::setFlash($e->getMessage());
		}
	}


	public function export() {
		if (!User::isLoggedIn()) {
			self::redirect('../login.php');
		}

		switch ($_GET['entity']) {
			case 'maps':
				// avoid retrieving maps as objects since each instantiation triggers
				// its own datastore query (inefficient)
				$maps = DataStore::getInstance()->getAllMapsAsArray();

				switch ($_GET['format']) {
					case 'csv':
						$this->outputMapsAsCSV($maps); break;
					case 'sql':
						$this->outputMapsAsSQL($maps); break;
					case 'svg':
						$this->outputMapsAsSVG($maps); break;
					case 'tab':
						$this->outputMapsAsTabDelimited($maps); break;
					case 'xml':
						$this->outputMapsAsXML($maps); break;
				}
				break;
			case 'collections':
				$cols = DataStore::getInstance()->getAllCollectionsAsArray();
				switch ($_GET['format']) {
					case 'sql':
						$this->outputCollectionsAsSQL($cols); break;
					case 'xml':
						$this->outputCollectionsAsXML($cols); break;
				}
				break;
		} // switch
	} // export()


	public final function getCurrentVersion() {
		$url = 'http://digital.library.unlv.edu/isis/current_version.php';
		$str = (int) file_get_contents($url);
		return $str;
	}


	public final function isCurrentVersion() {
		return ($this->getCurrentVersion()
		== Preference::getInstance()->getVersion()) ? true : false;
	}


	public function main() {
		if (!User::isLoggedIn()) {
			self::redirect('login.php');
		}
	}


	private function outputCollectionsAsSQL(array $cols) {
		$output = "CREATE TABLE IF NOT EXISTS collection (
	id int(10) unsigned NOT NULL auto_increment,
	name varchar(200) NOT NULL,
	alias varchar(50) NOT NULL,
	intro_url varchar(255) default NULL,
	base_url varchar(255) NOT NULL,
	path_to_thumbnail varchar(255) NOT NULL,
	path_to_oai varchar(255) NOT NULL,
	oai_id_prefix varchar(255) NOT NULL,
	organization varchar(255) default NULL,
	org_url varchar(255) default NULL,
	timestamp timestamp default CURRENT_TIMESTAMP,
	PRIMARY KEY (id)
);\n\n";
		foreach ($cols as $c) {
			$output .= sprintf("INSERT INTO collection(id, name, alias, intro_url, base_url, path_to_thumbnail, path_to_oai, oai_id_prefix, organization, org_url)\n\tVALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');\n",
				RDBMSDataStore::getInstance()->escape($c['id']),
				RDBMSDataStore::getInstance()->escape($c['name']),
				RDBMSDataStore::getInstance()->escape($c['alias']),
				RDBMSDataStore::getInstance()->escape($c['intro_url']),
				RDBMSDataStore::getInstance()->escape($c['base_url']),
				RDBMSDataStore::getInstance()->escape($c['path_to_thumbnail']),
				RDBMSDataStore::getInstance()->escape($c['path_to_oai']),
				RDBMSDataStore::getInstance()->escape($c['oai_id_prefix']),
				RDBMSDataStore::getInstance()->escape($c['organization']),
				RDBMSDataStore::getInstance()->escape($c['org_url'])
			);
		}
		header('Content-type: text/plain; charset=utf-8');
		echo $output; die;
	} // outputCollectionsAsSQL()


	private function outputCollectionsAsXML(array $cols) {
		$sxml = new SimpleXMLElement('<collections/>');
		foreach ($cols as $c) {
			$col = $sxml->addChild('collection');
			$col->addChild('id', String::paranoid($c['id']));
			$col->addChild('name', String::xmlentities($c['name']));
			$col->addChild('intro_url', String::xmlentities($c['intro_url']));
			$cdm = $col->addChild('contentdm');
			$cdm->addChild('alias',
				String::paranoid($c['alias'], array('_', '/')));
			$cdm->addChild('base_url', String::xmlentities($c['base_url']));
			$cdm->addChild('path_to_thumbnail',
				String::paranoid($c['path_to_thumbnail'],
					array('/', '.', '_', '-')));
			$cdm->addChild('path_to_oai',
				String::paranoid($c['path_to_oai'],
					array('/', '.', '_', '-')));
			$cdm->addChild('oai_id_prefix',
				String::paranoid($c['oai_id_prefix'], array(':', '.', '-')));
			$org = $col->addChild('organization');
			$org->addChild('name', String::xmlentities($c['organization']));
			$org->addChild('url', String::xmlentities($c['org_url']));
		}
		header('Content-type: text/xml; charset=utf-8');
		echo $sxml->asXML(); die;
	} // outputCollectionsAsXML()


	private function outputMapsAsCSV(array $maps) {
		$allowed_chars = array('.', '-');
		$output = "ID,collection ID,pointer,wlong,elong,slat,nlat\n";
		foreach ($maps as $m) {
			$output .= sprintf("%d,%d,%d,%f,%f,%f,%f\n",
				String::paranoid($m['id']),
				String::paranoid($m['collection_id']),
				String::paranoid($m['ptr']),
				String::paranoid($m['long_w'], $allowed_chars),
				String::paranoid($m['long_e'], $allowed_chars),
				String::paranoid($m['lat_s'], $allowed_chars),
				String::paranoid($m['lat_n'], $allowed_chars)
			);
		}
		header('Content-type: text/plain; charset=utf-8');
		echo $output; die;
	} // outputMapsAsCSV()


	private function outputMapsAsSQL(array $maps) {
		$allowed_chars = array('.', '-');
		$output = "CREATE TABLE IF NOT EXISTS map (
	id int(10) unsigned NOT NULL auto_increment,
	cisoptr int(10) unsigned NOT NULL,
	collection_id int(10) unsigned NOT NULL,
	lat_s float default NULL,
	lat_n float default NULL,
	long_w float default NULL,
	long_e float default NULL,
	timestamp timestamp NULL default CURRENT_TIMESTAMP,
	PRIMARY KEY (id),
	KEY collection_id (collection_id),
	CONSTRAINT map_ibfk_1 FOREIGN KEY (collection_id) REFERENCES collection (id)
		ON UPDATE CASCADE ON DELETE CASCADE
);\n\n";
		foreach ($maps as $m) {
			$output .= sprintf("INSERT INTO map(id, collection_id, lat_s, lat_n, long_w, long_e)\n\tVALUES (%d, %d, %d, %f, %f, %f, %f);\n",
				String::paranoid($m['id']),
				String::paranoid($m['collection_id']),
				String::paranoid($m['ptr']),
				String::paranoid($m['long_w'], $allowed_chars),
				String::paranoid($m['long_e'], $allowed_chars),
				String::paranoid($m['lat_s'], $allowed_chars),
				String::paranoid($m['lat_n'], $allowed_chars)
			);
		}
		header('Content-type: text/plain; charset=utf-8');
		echo $output; die;
	} // outputMapsAsSQL()


	private function outputMapsAsSVG(array $maps) {
		$allowed_chars = array('.', '-');
		$sxml = new SimpleXMLElement('<g/>');
		foreach ($maps as $m) {
			$lev2 = $sxml->addChild('rect');
			$lev2->addAttribute('id', String::paranoid($m['id']));
			$lev2->addAttribute('collection_id',
				String::paranoid($m['collection_id']));
			$lev2->addAttribute('ptr', String::paranoid($m['ptr']));
			$lev2->addAttribute('x',
			  String::paranoid($m['long_w'], $allowed_chars));
			$lev2->addAttribute('y',
			  String::paranoid($m['lat_n'], $allowed_chars));
			$lev2->addAttribute('width',
			  String::paranoid($m['long_e'] - $m['long_w'], $allowed_chars));
			$lev2->addAttribute('height',
			  String::paranoid($m['lat_n'] - $m['lat_s'], $allowed_chars));
		}
		header('Content-type: image/svg+xml; charset=utf-8');
		echo $sxml->asXML(); die;
	} // outputMapsAsSVG()


	private function outputMapsAsTabDelimited(array $maps) {
		$allowed_chars = array('.', '-');
		$output = "ID\tcollection ID\tCISOPTR\twlong\telong\tslat\tnlat\n";
		foreach ($maps as $m) {
			$output .= sprintf("%d\t%d\t%d\t%f\t%f\t%f\t%f\n",
				String::paranoid($m['id']),
				String::paranoid($m['collection_id']),
				String::paranoid($m['ptr']),
				String::paranoid($m['long_w'], $allowed_chars),
				String::paranoid($m['long_e'], $allowed_chars),
				String::paranoid($m['lat_s'], $allowed_chars),
				String::paranoid($m['lat_n'], $allowed_chars)
			);
		}
		header('Content-type: text/plain; charset=utf-8');
		echo $output; die;
	} // outputMapsAsTabDelimited()


	private function outputMapsAsXML(array $maps) {
		$allowed_chars = array('.', '-');
		$sxml = new SimpleXMLElement('<maps/>');
		foreach ($maps as $m) {
			$map = $sxml->addChild('map');
			$map->addChild('id', String::paranoid($m['id']));
			$map->addChild('collection_id',
				String::paranoid($m['collection_id']));
			$map->addChild('ptr', String::paranoid($m['ptr']));
			$map->addChild('lat_n',
				String::paranoid($m['lat_n'], $allowed_chars));
			$map->addChild('lat_s',
				String::paranoid($m['lat_s'], $allowed_chars));
			$map->addChild('long_e',
				String::paranoid($m['long_e'], $allowed_chars));
			$map->addChild('long_w',
				String::paranoid($m['long_w'], $allowed_chars));
		}
		header('Content-type: text/xml; charset=utf-8');
		echo $sxml->asXML(); die;
	} // outputMapsAsXML()

} // AdminController

?>
